import { isObject, isArray, isString, isNumeric, isPlainObject, isBoolean, isDef } from './types'
// @ts-ignore
import { digit } from './math'

/**
 * 获取设备信息
 * @param {String} key 关键字 { os, ie, wx, ios, android }
 * @returns {Object} { os: '', ie: '', wx: '', ios: '', android: '' }
 * @example
 *  getDevice(key)
 */
export const getDevice = key => {
  const agent = navigator.userAgent.toLowerCase()

  const getVersion = label => {
    const exp = new RegExp(label + '/([^\\s\\_\\-]+)')
    label = (agent.match(exp) || [])[1]
    return label || false
  }

  const getOs = (function() {
    if (/windows/.test(agent)) {
      return 'windows'
    } else if (/linux/.test(agent)) {
      return 'linux'
    } else if (/iphone|ipod|ipad|ios/.test(agent)) {
      return 'ios'
    } else if (/mac/.test(agent)) {
      return 'mac'
    } else if (/android/.test(agent)) {
      return 'android'
    }
  }())

  const getIe = (function() {
    return (!!window.ActiveXObject || 'ActiveXObject' in window) ? (
      (agent.match(/msie\s(\d+)/) || [])[1] || '11' // 由于ie11并没有msie的标识
    ) : false
  }())

  const result = {
    os: getOs,
    ie: getIe,
    wx: getVersion('micromessenger'),
    ios: getOs === 'ios',
    android: getOs === 'android'
  }

  if (key && !result[key]) {
    result[key] = getVersion(key)
  }

  return result
}

/**
 * 获取屏幕尺寸，与样式分割一致(3:larger, 2:medium, 1:small, 0:mini)
 * @return {Number}
 * @example
 *  getScreen()
 */
export const getScreen = function() {
  const { body } = document
  const bodyWidth = body.getBoundingClientRect ? body.getBoundingClientRect().width : body.offsetWidth
  return bodyWidth > 1200 ? 3 : bodyWidth > 992 ? 2 : bodyWidth > 768 ? 1 : 0
}

/**
 * 序列化，出错返回本身
 * @param  {...any} args anything
 * @example
 *  stringify({name: 'jack', age: 1}) => {"name":"jack","age":1}
 *  stringify(NaN) => 'null'
 *  stringify(null) => 'null'
 *  stringify(undefined) => undefined
 *  stringify(Math) => {}
 *  stringify(window) => window // error
 */
export function stringify(...args) {
  let ret = args[0]

  try {
    ret = JSON.stringify.apply(JSON, args)
  } catch (e) {
    //
  }

  return ret
}

/**
 * 反序列化，出错返回本身
 * @param  {...any} args anything
 * @example
 *  parse(window) => window // error
 *  parse(undefined) => undefined // error
 *  parse(NaN) => NaN // error
 *  parse(null) => null
 *  parse({"name":"jack","age":1}) => {name: "jack", age: 1}
 *  parse(1) => 1
 *  parse(-0) => 0
 */
export function parse(...args) {
  let ret = args[0]

  try {
    ret = JSON.parse.apply(JSON, args)
  } catch (e) {
    //
  }

  return ret
}

/**
 * 生成 uuid
 */
export function uuid() {
  return Math.random().toString(36).slice(4)
}

/**
 * 复制一个值
 * @param {Object} data
 */
export function copy(data) {
  let copyOne = null

  if (isObject(data)) {
    copyOne = {}
    for (const key in data) {
      copyOne[key] = copy(data[key])
    }
  } else if (isArray(data)) {
    copyOne = []
    data.forEach(function(index) {
      copyOne.push(copy(index))
    })
  } else {
    copyOne = data
  }
  return copyOne
}

/**
 * 创建一个新数组，包含原数组中所有的非假值元素。
 *  例如 false, null, 0, "", undefined, 和 NaN 都是被认为是“假值”。
 * @param {Array} array 待处理的数组
 */
export function compactArray(array) {
  const newArray = []

  if (isArray(array)) {
    for (let i = 0; i < array.length; i++) {
      array[i] && newArray.push(array[i])
    }
  }

  return newArray
}

export function timeAgo(time, onlyDate) {
  const arr = [[], []]
  let stamp = new Date().getTime() - new Date(time).getTime()

  // 返回具体日期
  if (stamp > 1000 * 60 * 60 * 24 * 8) {
    // @ts-ignore
    stamp = new Date(time)
    // @ts-ignore
    arr[0][0] = digit(stamp.getFullYear(), 4)
    // @ts-ignore
    arr[0][1] = digit(stamp.getMonth() + 1)
    // @ts-ignore
    arr[0][2] = digit(stamp.getDate())

    // 是否输出时间
    if (!onlyDate) {
      // @ts-ignore
      arr[1][0] = digit(stamp.getHours())
      // @ts-ignore
      arr[1][1] = digit(stamp.getMinutes())
      // @ts-ignore
      arr[1][2] = digit(stamp.getSeconds())
    }
    return arr[0].join('-') + ' ' + arr[1].join(':')
  }

  // 30天以内，返回“多久前”
  if (stamp >= 1000 * 60 * 60 * 24) {
    return ((stamp / 1000 / 60 / 60 / 24) | 0) + '天前'
  } else if (stamp >= 1000 * 60 * 60) {
    return ((stamp / 1000 / 60 / 60) | 0) + '小时前'
  } else if (stamp >= 1000 * 60 * 2) { // 2分钟以内为：刚刚
    return ((stamp / 1000 / 60) | 0) + '分钟前'
  } else if (stamp < 0) {
    return '未来'
  } else {
    return '刚刚'
  }
}

/**
 * 转义 html
 * @param {string} html html代码
 */
export function escape(html) {
  return String(html || '').replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&amp;')
    .replace(/</g, '&lt;').replace(/>/g, '&gt;')
    .replace(/'/g, '&#39;').replace(/"/g, '&quot;')
}

/**
 * 格式化时间
 * @param {Date|string} time 时间，缺省则为当前时间
 * @param {string} format 格式，缺省则为 yyyy-MM-dd HH:mm:ss
 * @example
 *  toDateString() => 2020-03-05 11:46:22
 *  toDateString('2020-03-05 11:46:22', 'yyyy-MM-dd') => 2020-03-05
 */
export function toDateString(time = new Date(), format = 'yyyy-MM-dd HH:mm:ss') {
  // @ts-ignore
  time = isString(time) ? time/** as string */.replace(/-/g, '/') : time
  const date = new Date(time)
  const ymd = [
    digit(date.getFullYear(), 4),
    digit(date.getMonth() + 1),
    digit(date.getDate())
  ]
  const hms = [
    digit(date.getHours()),
    digit(date.getMinutes()),
    digit(date.getSeconds())
  ]

  return format.replace(/yyyy/g, ymd[0])
    .replace(/MM/g, ymd[1])
    .replace(/dd/g, ymd[2])
    .replace(/HH/g, hms[0])
    .replace(/mm/g, hms[1])
    .replace(/ss/g, hms[2])
}

/**
 * 移除所有的空白字符
 * @param {string} string
 */
export function trimAll(string) {
  return isString(string) ? string.replace(/\s+/g, '') : string
}

/**
 * 对象深度取值
 * @param {Object} obj 待遍历的对象
 * @param {Array|string} keyPath 路径
 * @example
 *  var object = { id: { v: 'a' }, b: 2 }
 *  getKeyByPath(object,'id.v') => a
 *  getKeyByPath(object, ['id', 'v']) => a
 */
export function getKeyByPath(obj, keyPath) {
  if (!isObject(obj)) return null

  let array = null
  if (this.isArray(keyPath)) {
    array = keyPath
  } else if (this.isString(keyPath)) {
    // @ts-ignore
    array = keyPath.split('.')
  }
  if (array == null || array.length === 0) {
    return null
  }
  let value = null
  let key = array.shift()
  const keyTest = key.match(new RegExp('^(\\w+)\\[(\\d+)\\]$'))
  if (keyTest) {
    key = keyTest[1]
    const index = keyTest[2]
    value = obj[key]
    if (this.isArray(value) && value.length > index) {
      value = value[index]
    }
  } else {
    value = obj[key]
  }

  if (array.length > 0) {
    return getKeyByPath(value, array)
  }
  return value
}

/**
 * 对象深度赋值
 * @param {Object} obj 待处理的对象
 * @param {Array|string} keyPath 路径
 * @param {any} value 替换的值
 * @param {Object} orignal 结果
 * @example
 *  var object = { id: { v: ['a', 'b'] }, b: 2 }
 *  setKeyByPath(object, 'id.v[1]', 'c') => { id: { v: ['a','c'] }, b: 2 }
 */
export function setKeyByPath(obj, keyPath, value, orignal) {
  if (!isObject(obj)) return false

  let array = null
  if (isArray(keyPath)) {
    array = keyPath
  } else if (isString(keyPath)) {
    // @ts-ignore
    array = keyPath.split('.')
    orignal = obj
  }
  if (array == null || array.length === 0) {
    return false
  }
  let children = null
  let index = 0
  let key = array.shift()
  const keyTest = key.match(new RegExp('^(\\w+)\\[(\\d+)\\]$'))
  if (keyTest) {
    key = keyTest[1]
    index = keyTest[2]
    children = obj[key]
    if (isArray(children) && children.length > index) {
      if (array.length > 0) {
        return setKeyByPath(children[index], array, value, orignal)
      }
      children[index] = value
    }
  } else {
    if (array.length > 0) {
      return setKeyByPath(obj[key], array, value, orignal)
    }
    obj[key] = value
  }
  return orignal
}

/**
 * 截取字符
 * @param {string} string
 * @param {number} length
 */
export function cut(string, length) {
  return isString(string) && string.length > length ? string.substring(0, length) + '...' : string
}

/**
 * 判断字符以指定目标开头
 * @param {string|number} string
 * @param {string} tag 指定目标
 */
export function beginWith(string, tag) {
  string = isNumeric(string) ? String(string) : string
  // @ts-ignore
  return isString(string) && string.substring(0, tag.length) === tag
}

/**
 * 判断字符以指定目标结尾
 * @param {string|number} string
 * @param {string} tag 指定目标
 */
export function endWith(string, tag) {
  string = isNumeric(string) ? String(string) : string
  // @ts-ignore
  return isString(string) && string.substring(string.length - tag.length) === tag
}

/**
 * 字节长度
 * @param {string|number} string 待处理的数据
 * @param {boolean} loose 是否松散处理
 * @example
 *  byteSize('你好', true) => 2
 *  byteSize('你好', false) => 4
 *  byteSize('hello', true) => 5
 *  byteSize('hello') => 5
 *  byteSize(window) => 0
 */
export function byteSize(string, loose) {
  string = isNumeric(string) ? String(string) : string
  // @ts-ignore
  return isString(string) ? loose ? string.length : string.replace(/[^x00-xff]/g, '__').length : 0
}

/**
 * 文本节点长度，根据页面的根字体大小计算
 * @param {string|number} string 待处理的字符
 * @example
 *  // baseSize: 14
 *  textNodeSize(1) => 8
 *  textNodeSize('h') => 8
 *  textNodeSize('w') => 10
 *  textNodeSize('hello') => 30
 *  textNodeSize('你好') => 28
 */
export function textNodeSize(string) {
  const body = document.body || document.getElementsByTagName('body')
  const div = document.createElement('div')
  let width = -1
  div.style.position = 'fixed'
  div.style.top = '-9999px'
  div.style.left = '-9999px'
  div.style.zIndex = '-9999'
  div.style.opacity = '0'
  // @ts-ignore
  div.innerHTML = string
  // @ts-ignore
  body.appendChild(div)
  width = div.offsetWidth
  // @ts-ignore
  body.removeChild(div)
  return width
}

/**
 * 获取URL或者类似URL上的参数
 * @param {string} name
 * @param {string} search
 * @example
 *  var ep = 's?wd=pp'
 *  getURLParam('wd', ep) => pp
 */
export function getURLParam(name, search) {
  // @ts-ignore
  return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(search || location.search) || [true, ''])[1].replace(/\+/g, '%20')) || null
}

/**
 * @param {Object} json
 * @returns {Array}
 * @example
 *  stringifyParam({name:'jack', sex:undefined, score:0, age: 18, like: []})
 *    => name=jack&score=0&age=18&like=
 */
export function stringifyParam(json) {
  // @ts-ignore
  if (!json) return ''
  // @ts-ignore
  return compactArray(
    Object.keys(json).map(key => {
      if (json[key] === undefined) return ''
      return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
    })
  ).join('&')
}

/**
 * 合并 URL 参数
 * @param {string} url
 * @param {Object} option
 * @param {boolean} compact 是否清楚假值
 * @example
 *  stringifyURLParam('www.baidu.com', {md: 'pp'}) => 'www.baidu.com?md=pp'
 *  stringifyURLParam('www.baidu.com', {md: 'pp', sex: false}) => 'www.baidu.com?md=pp'
 *  stringifyURLParam('www.baidu.com', {md: 'pp', sex: false}, false) => 'www.baidu.com?md=pp&sex=false'
 */
export function stringifyURLParam(url, option, compact = true) {
  url = isString(url) ? url : ''
  url = endWith(url, '?') ? url : url.concat('?')

  if (isPlainObject(option)) {
    if (compact) {
      url = url + stringifyParam(option)
    } else {
      for (const key in option) {
        const value = option[key]
        url += key + '=' + (/^[\u4e00-\u9fa5_a-zA-Z0-9]+$/.test(value) ? encodeURIComponent(value) : (isDef(value) ? value : '')) + '&'
      }
      url = url.substring(0, url.length - 1)
    }
  }

  return url
}

/**
 * 获取 URL 参数
 * @param {string} url
 * @example
 *  parseURLParam('www.baidu.com?md=pp') => { md: 'pp' }
 */
export function parseURLParam(url) {
  const search = isString(url) ? url.split('?')[1] : ''
  if (!search) {
    return {}
  }
  return parse(
    '{"' +
      decodeURIComponent(search)
        .replace(/"/g, '\\"')
        .replace(/&/g, '","')
        .replace(/=/g, '":"')
        .replace(/\+/g, ' ') +
      '"}'
  )
}

/**
 * 格式化路径
 * @param  {...any} args 路径
 * @example
 *  resolvePath('test', 'hello', 'world') => /test/hello/world
 *  resolvePath(false, 'world', 'https://baidu.com/test', '/test/hello') => https://baidu.com/test/test/hello
 *  resolvePath(true, 'world', 'https://baidu.com/test', '/test/hello') => world/https://baidu.com/test/test/hello
 *  resolvePath(true, 'https://baidu.com', 'https://baidu.com/test/hello', '/test/hello') => https://baidu.com/test/hello
 */
export function resolvePath(...args) {
  let ret = ''
  const isIgnore = isBoolean(args[0]) && args[0]
  const combineURLs = function(baseURL, relativeURL) {
    if (!baseURL) return relativeURL

    baseURL = baseURL.replace(/\/+$/, '')
    relativeURL = '/' + relativeURL.replace(/^\/+/, '')

    return baseURL + relativeURL
  }
  const resolveURLs = function(baseURL, relativeURL) {
    return /^http(s)?:\/\/.+/.test(relativeURL) || relativeURL.indexOf(baseURL) > -1 ? relativeURL : combineURLs(baseURL, relativeURL)
  }

  isIgnore && args.shift()

  for (let i = 0; i < args.length; i++) {
    ret = isIgnore ? combineURLs(ret, args[i]) : resolveURLs(ret, args[i])
  }

  return ret
}

/**
 * 注入样式
 * @param {string} css 样式
 * @param {object} ref
 * @param {string} ref.id 标识位
 * @param {string} ref.insertAt 插入的位置，默认紧跟尾部，若插入到首要位置，可以使用 'top'
 */
export function styleInject(css, ref = { id: '', insertAt: '' }) {
  const insertAt = ref.insertAt
  const insertId = ref.id

  if (!css || typeof document === 'undefined') {
    return
  }

  const head = document.head || document.getElementsByTagName('head')[0]
  const style = document.createElement('style')
  style.type = 'text/css'
  if (insertId) {
    style.id = insertId
  }

  if (insertAt === 'top') {
    if (head.firstChild) {
      head.insertBefore(style, head.firstChild)
    } else {
      head.appendChild(style)
    }
  } else {
    head.appendChild(style)
  }

  // @ts-ignore
  if (style.styleSheet) {
    // @ts-ignore
    style.styleSheet.cssText = css
  } else {
    style.appendChild(document.createTextNode(css))
  }
}

export const NOOP = () => {}
export const NO = () => false

/**
 * 做个 map 集合，用于枚举
 * @param {string} str 一段字符串，使用,隔开
 * @param {boolean} expectsLowerCase 是否需要转换成小写
 */
export function makeMap(str, expectsLowerCase = false) {
  const map = Object.create(null)
  const list = str.split(',')
  for (let i = 0; i < list.length; i++) {
    map[list[i]] = true
  }
  return expectsLowerCase
    ? function(val) { return map[val.toLowerCase()] }
    : function(val) { return map[val] }
}
